package com.jiangchao.xmldb.server;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.alibaba.fastjson.JSONObject;
import com.jiangchao.xmldb.annotation.Index;
import com.jiangchao.xmldb.annotation.Table;

/**
 * 核心操作
 * 
 * @author jiangchao
 *
 */
public class DbDao {

	/**
	 * 数据目录路径
	 */
	private String dataDirPath;
	/**
	 * 数据库名
	 */
	private String dbName;

	/**
	 * 构造方法初始化所需的参数
	 * 
	 * @param dataDirPath
	 * @param dbName
	 */
	public DbDao(String dataDirPath, String dbName) {
		this.dataDirPath = dataDirPath;
		this.dbName = dbName;
	};

	/**
	 * 简单创建数据库
	 * 
	 * 本数据库采用xml文件的形式存储数据，创建数据库的本质就是创建对应的文件目录
	 * 
	 * @param dataDirPath 数据目录路径
	 * @param dbName      数据库名称
	 * @throws Exception
	 */
	public void create() throws Exception {
		String dirPath = this.dataDirPath + File.separator + this.dbName;
		File dbDir = new File(dirPath);
		if (!dbDir.exists()) {
			dbDir.mkdirs();
		}
		if (!dbDir.isDirectory()) {
			throw new Exception(dataDirPath + "不是一个目录");
		}

	}

	/**
	 * 创建表 本数据库采用xml文件的形式存储数据，创建数据库的本质就是创建对应的文件目录，表的本质是一个xml文件
	 * 
	 * @param dataDirPath 数据目录路径
	 * @param dbName      数据库名称
	 * @param clz         数据库实体类型
	 * @throws IOException
	 */
	public void createTable(Class clz) throws IOException {
//		解析数据库实体的Table注解获取表名
		Table tableAnnotation = (Table) clz.getAnnotation(Table.class);
		String tableName = tableAnnotation.value();
		String tableFilePath = this.dataDirPath + File.separator + this.dbName + File.separator + tableName + ".xml";
//		构造对应的文件对象
		File file = new File(tableFilePath);
		if (file.createNewFile()) {
//			创建基本xml文件内容
			Document document = DocumentHelper.createDocument();
			Element root = document.addElement("table");
			root.addAttribute("name", tableName);
			write(tableFilePath, document);
		}
	}

	/**
	 * 数据库新增、更新方法
	 * 
	 * @param object 需要持久化的数据库实体对象
	 * @return 返回该对象
	 * @throws IntrospectionException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws DocumentException
	 * @throws IOException
	 */
	public Object save(Object object) throws IntrospectionException, IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, DocumentException, IOException {
//		获得类对象
		Class objectClass = object.getClass();
//		利用内省的方式获得uuid属性
		String uuid = getUUID(object);
//		判断是新增还是修改
		if ("".equals(uuid) || uuid == null) {
//			新增
			insert(object);
		} else {
//			修改
			update(object);
		}

		return object;
	}

	/**
	 * 数据库删除方法
	 * 
	 * @param object 需要持久化的数据库实体对象
	 * @throws DocumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws IntrospectionException
	 * @throws IOException
	 */
	public void remove(Object object) throws DocumentException, IllegalAccessException, InvocationTargetException,
			IntrospectionException, IOException {
		Class clz = object.getClass();
//		获取表名
		String tableName = getTableName(object);
		String tableFilePath = this.dataDirPath + File.separator + this.dbName + File.separator + tableName + ".xml";

		SAXReader reader = new SAXReader();
		Document document = reader.read(tableFilePath);
		Element root = document.getRootElement();
		String uuid = getUUID(object);
//		遍历内容，找到对应的document进行删除
		for (Iterator<Element> it = root.elementIterator("document"); it.hasNext();) {
			Element documentElement = it.next();
			JSONObject jsonObject = JSONObject.parseObject(documentElement.getText());
			if (jsonObject.getString("_uuid").equals(uuid)) {
				root.remove(documentElement);
			}
		}
		write(tableFilePath, document);
	}

	public List<JSONObject> findAll(Class clz) throws DocumentException, IllegalAccessException,
			InvocationTargetException, IntrospectionException, IOException, InstantiationException {
		List<JSONObject> list = new ArrayList<JSONObject>();
//获取表名
		String tableName = getTableName(clz.newInstance());
		String tableFilePath = this.dataDirPath + File.separator + this.dbName + File.separator + tableName + ".xml";

		SAXReader reader = new SAXReader();
		Document document = reader.read(tableFilePath);
		Element root = document.getRootElement();
		for (Iterator<Element> it = root.elementIterator("document"); it.hasNext();) {
			Element documentElement = it.next();
			JSONObject jsonObject = JSONObject.parseObject(documentElement.getText());
			list.add(jsonObject);
		}
		return list;

	}

	private String getUUID(Object object)
			throws IntrospectionException, IllegalAccessException, InvocationTargetException {
		// 使用内省的方式获取_uuid
		PropertyDescriptor pd = new PropertyDescriptor("_uuid", object.getClass());
		String uuid = (String) pd.getReadMethod().invoke(object, null);
		return uuid;
	}

	private void insert(Object object) throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, DocumentException, IntrospectionException, IOException {
		Class clz = object.getClass();
//		获取_uuid对应的写方法并写入随机uuid
		PropertyDescriptor pd = new PropertyDescriptor("_uuid", clz);
		String uuid = UUID.randomUUID().toString();
		pd.getWriteMethod().invoke(object, uuid);
//		获取表名
		String tableName = getTableName(object);
		String tableFilePath = this.dataDirPath + File.separator + this.dbName + File.separator + tableName + ".xml";

		SAXReader reader = new SAXReader();
		Document document = reader.read(tableFilePath);
		Element root = document.getRootElement();
		Element documentElement = root.addElement("document");

		Map<String, String> indexedColumnMap = getIndexedColumns(object);
		for (String key : indexedColumnMap.keySet()) {
			documentElement.addAttribute("idx_" + key, indexedColumnMap.get(key));
		}
		documentElement.addText(getContent(object));
		write(tableFilePath, document);

	}

	private void update(Object object) throws DocumentException, IllegalArgumentException, IllegalAccessException,
			IOException, InvocationTargetException, IntrospectionException {
		Class clz = object.getClass();
//		获取表名
		String tableName = getTableName(object);
		String tableFilePath = this.dataDirPath + File.separator + this.dbName + File.separator + tableName + ".xml";

		SAXReader reader = new SAXReader();
		Document document = reader.read(tableFilePath);
		Element root = document.getRootElement();
		String uuid = getUUID(object);
		for (Iterator<Element> it = root.elementIterator("document"); it.hasNext();) {
			Element documentElement = it.next();
			JSONObject jsonObject = JSONObject.parseObject(documentElement.getText());
			if (jsonObject.getString("_uuid").equals(uuid)) {
				documentElement.setText(getContent(object));
			}
		}

		write(tableFilePath, document);
	}

	/**
	 * 持久化到文件
	 * 
	 * @param tableFilePath 表对应的文件路径
	 * @param document      xml对象
	 * @throws IOException
	 */
	private void write(String tableFilePath, Document document) throws IOException {
		FileWriter out = new FileWriter(tableFilePath);
		document.write(out);
		out.close();
	}

	/**
	 * 获取对应实体类的表名
	 * 
	 * @param po 数据库实体类
	 * @return 表名
	 */
	private String getTableName(Object po) {
		Class clz = po.getClass();
		Table tableAnnotation = (Table) clz.getAnnotation(Table.class);
		String tableName = tableAnnotation.value();
		return tableName;
	}

	/**
	 * 获取对应实体类中需要增加索引的字段
	 * 
	 * @param po 数据库实体类
	 * @return 表名
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	private Map<String, String> getIndexedColumns(Object po) throws IllegalArgumentException, IllegalAccessException {
		Map<String, String> map = new HashMap<String, String>();
		Class clz = po.getClass();
//		获取每个字段
		Field[] fields = clz.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Index.class)) {
				field.setAccessible(true);
				map.put(field.getName(), field.get(po).toString());
				field.setAccessible(false);
			}
		}
		return map;
	}

	/**
	 * 获取对应实体类中所有字段
	 * 
	 * @param po 数据库实体类
	 * @return 表名
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	private String getContent(Object po) throws IllegalArgumentException, IllegalAccessException {
		Map<String, Object> map = new HashMap<String, Object>();
		Class clz = po.getClass();
//		获取每个字段
		Field[] fields = clz.getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true);
			map.put(field.getName(), field.get(po));
			field.setAccessible(false);
		}
		return JSONObject.toJSONString(map);
	}

}
